package test;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;

public class DistributionTreePrinter {
	private static LinkedList<TestNode> printQueue = new LinkedList<TestNode>();
	private static char[][] printArray;
	private static final int PRINT_LINES = 6;

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//ArrayList<ArrayList<TestNode>> nodeLayout;
		TestNode node = new TestNode();
		node.setNodeName("Test Node");
		
		TestNode child1 = new TestNode();
		child1.setNodeName("Test Child 1");
		
		TestNode childChild = new TestNode();
		childChild.setNodeName("Child 1 1");
		child1.addChild(childChild);
		childChild = new TestNode();
		childChild.setNodeName("The Child 1 2");
		child1.addChild(childChild);
		node.addChild(child1);
		
		TestNode child = new TestNode();
		child.setNodeName("Child 2");
		childChild = new TestNode();
		childChild.setNodeName("Child 2 1");
		child.addChild(childChild);
		childChild = new TestNode();
		childChild.setNodeName("The Child 2 2");
		child.addChild(childChild);
		node.addChild(child);
		
		child = new TestNode();
		child.setNodeName("The Child 3");
		childChild = new TestNode();
		childChild.setNodeName("Child 3 1");
		//child.addChild(childChild);
		childChild = new TestNode();
		childChild.setNodeName("The Child 3 2");
		//child.addChild(childChild);
		node.addChild(child);
		
		int maxWidth = getTreeMaxWidth(node)+150;
		int maxDepth = getTreeMaxDepth(node);
		int printDepth = maxDepth * PRINT_LINES; //multiply by number of printed lines for each level
		System.out.println("Max Width: " + getTreeMaxWidth(node));
		System.out.println("Max Depth: " + getTreeMaxDepth(node));

		System.out.println("Offset: " + getLeftOffset(node));
		
		printArray = new char[printDepth][maxWidth];
		initPrintArray();
		
		plotTree(node, 0, 0);
		
		printTree();
		
		//printTree(node);
		
		//printLevel(node.getChildMap());
		//printLevel(child1.getChildMap());
		
		//printNode(node);
		//printLink(node);
	}
	
	private static void printTree(){
		for(int i = 0; i < printArray.length; i++){
			for(int j = 0; j < printArray[i].length; j++){
				System.out.print(printArray[i][j]);
			}
			System.out.print("\n");
		}
	}
	
	private static void initPrintArray(){
		char initChar = ' ';
		for(int i = 0; i < printArray.length; i++){
			for(int j = 0; j < printArray[i].length; j++){
				printArray[i][j] = initChar;
			}
		}
	}
	
	private static void plotTree(TestNode tree, int offset, int depth){
		int childOffset = offset;
		plotNode(tree, offset, depth);
		for(TestNode child : tree.getChildMap().values()){
			plotTree(child, childOffset, depth+1);
			childOffset += getTreeMaxWidth(child);
		}
		if(tree.getChildMap().size() > 0){
			plotLinks(tree.getChildMap(), offset, depth, childOffset);
		}
	}
	
	private static void plotLinks(LinkedHashMap<String, TestNode> children, int offset, int depth, int lastOffset){
		
			int topOffset = (depth+1) * PRINT_LINES - 1;
			
			int i = 0;
			TestNode firstChild = null;
			TestNode lastChild = null;
			for(TestNode tempChild : children.values()){
				if(i==0){
					firstChild = tempChild;
				}
				lastChild = tempChild;
				i++;
			}
			
			int leftOffset = offset + getLeftOffset(firstChild) + getNodeWidth(firstChild) / 2;
			int rightOffset = lastOffset - getNodeWidth(lastChild) / 2; 
			
			for(int j = leftOffset; j < rightOffset; j++){
				printArray[topOffset][j] = '_';
			}
//			printArray[topOffset][leftOffset] = '@';
//			printArray[topOffset][rightOffset] = '$';
		}
	
	
	private static void plotNode(TestNode node, int offset, int depth){
		int leftOffset = offset + getLeftOffset(node);
		int topOffset = depth * PRINT_LINES;
		
		String nodeName = node.getNodeName();
		int nameLength = nodeName.length();
		int nodeWidth = getNodeWidth(node);
		int halfNodeWidth = nodeWidth / 2;
		
		//create lead-in
		printArray[topOffset][leftOffset + halfNodeWidth] = '|';
		topOffset++;
		
		for(int i = leftOffset+1; i < leftOffset+nodeWidth-1; i++){
			printArray[topOffset][i] = '_';
		}
		printArray[topOffset][leftOffset + halfNodeWidth] = '|';
		topOffset++;
		
		printArray[topOffset][leftOffset] = '|';
		printArray[topOffset][leftOffset+1] = ' ';
		for(int i = 0; i < nameLength; i++){
			printArray[topOffset][leftOffset+2+i] = nodeName.charAt(i);
		}
		printArray[topOffset][leftOffset+nodeWidth-2] = ' ';
		printArray[topOffset][leftOffset+nodeWidth-1] = '|';
		topOffset++;
		
		printArray[topOffset][leftOffset] = '|';
		for(int i = leftOffset+1; i < leftOffset+nodeWidth-1; i++){
			printArray[topOffset][i] = '_';
		}
		printArray[topOffset][leftOffset+nodeWidth-1] = '|';
		topOffset++;
		
		//create lead-out
		printArray[topOffset][leftOffset + halfNodeWidth] = '|';
	
	}
	
	
	
	private static int getLeftOffset(TestNode node){
		int leftOffset = (getTreeMaxWidth(node)- getNodeWidth(node)) / 2 ;
		
		return leftOffset;
	}
	
	private static void printTree(TestNode node){
		printQueue.add(node);
		TestNode dcNode = new TestNode();
		dcNode.setNodeName("DC");
		printQueue.add(dcNode);
		TestNode tempNode;
		while((tempNode = printQueue.poll()) != null){
			if(tempNode.getNodeName().equals("DC")){
				if(printQueue.size() > 0){
					//print depth change line (link line)
					
					//add to end of queue
					printQueue.add(tempNode);
				}
			}else{
				for(TestNode childNode : tempNode.getChildMap().values()){
					printQueue.add(childNode);
				}
				//print node;
				printNode(tempNode);
			}
			
		}		
		
		
//		Put depth change onto queue
//		Put all children onto queue
//		print node
	}
	
	private static void printNode(TestNode node){
		String nodeName = node.getNodeName();
		int nameLength = nodeName.length();
		int nodeWidth = getNodeWidth(node);
		int halfNodeWidth = nodeWidth / 2;
		StringBuffer outBuffer = new StringBuffer();
		
		//create lead-in
		for(int i = 0; i < halfNodeWidth; i++){
			outBuffer.append(" ");
		}
		outBuffer.append("|");
		outBuffer.append("\n");
		for(int i = 0; i < halfNodeWidth; i++){
			outBuffer.append(" ");
		}
		outBuffer.append("|");
		outBuffer.append("\n");
		
		//create top line _______
		outBuffer.append(" ");
		outBuffer.append("_");
		for(int i = 0; i < nameLength; i++){
			outBuffer.append("_");
		}
		outBuffer.append("_");
		outBuffer.append("\n");
		
		//create name line |  name  |
		outBuffer.append("|");
		outBuffer.append(" ");
		outBuffer.append(nodeName);
		outBuffer.append(" ");
		outBuffer.append("|");
		outBuffer.append("\n");
		
		//create bottom line _______
		outBuffer.append("|");
		outBuffer.append("_");
		for(int i = 0; i < nameLength; i++){
			outBuffer.append("_");
		}
		outBuffer.append("_");
		outBuffer.append("|");
		outBuffer.append("\n");
		
		//create lead-out
		for(int i = 0; i < halfNodeWidth; i++){
			outBuffer.append(" ");
		}
		outBuffer.append("|");
		outBuffer.append("\n");
		for(int i = 0; i < halfNodeWidth; i++){
			outBuffer.append(" ");
		}
		outBuffer.append("|");
		outBuffer.append("\n");
		
		System.out.print(outBuffer);
	}
	
	private static void printLink(TestNode node){
		LinkedHashMap<String, TestNode> children = node.getChildMap();
		StringBuffer outBuffer = new StringBuffer();
		int totalWidth = 0;
		for(TestNode child : children.values()){
			totalWidth += getNodeWidth(child);
		}
		
		for(int i = 0; i < totalWidth; i++){
			outBuffer.append("_");
		}
		
		System.out.print(outBuffer);
	}
	
	private static int getChildrenWidth(TestNode node){
		LinkedHashMap<String, TestNode> children = node.getChildMap();
		int totalWidth = 0;
		for(TestNode child : children.values()){
			totalWidth += getNodeWidth(child);
		}
		
		return totalWidth;
	}
	
	private static void printChildren(TestNode node){
		
	}
	
	private static int getNodeWidth(TestNode node){
		return node.getNodeName().length() + 4;
	}
	
	private static void printLevel(Map<String, TestNode> nodeMap){
		StringBuffer outBuffer = new StringBuffer();
		
		for(TestNode node : nodeMap.values()){
			int width = getNodeWidth(node);
			int halfNodeWidth = width / 2;
			
			for(int i = 0; i < halfNodeWidth; i++){
				outBuffer.append(" ");
			}
			outBuffer.append("|");
			for(int i = 0; i < halfNodeWidth; i++){
				outBuffer.append(" ");
			}
			for(int i = 0; i < getChildrenWidth(node) + 1; i++){
				outBuffer.append(" ");
			}
		}
		outBuffer.append("\n");
		
		for(TestNode node : nodeMap.values()){
			int width = getNodeWidth(node);
			int halfNodeWidth = width / 2;
			
			outBuffer.append(" ");
			for(int i = 0; i < halfNodeWidth-1; i++){
				outBuffer.append("_");
			}
			outBuffer.append("|");
			for(int i = 0; i < halfNodeWidth-1; i++){
				outBuffer.append("_");
			}
			for(int i = 0; i < getChildrenWidth(node) + 1; i++){
				outBuffer.append(" ");
			}
			outBuffer.append(" ");
		}
		outBuffer.append("\n");
		
		for(TestNode node : nodeMap.values()){
			int width = getNodeWidth(node);
			
			outBuffer.append("|");
			outBuffer.append(" ");
			outBuffer.append(node.getNodeName());
			if(isEven(width)){
				outBuffer.append(" ");
			}
			outBuffer.append(" ");
			outBuffer.append("|");
			for(int i = 0; i < getChildrenWidth(node) + 1; i++){
				outBuffer.append(" ");
			}
		}
		outBuffer.append("\n");
		
		for(TestNode node : nodeMap.values()){
			int width = getNodeWidth(node);
			int halfNodeWidth = width / 2;
			
			outBuffer.append("|");
			for(int i = 0; i < halfNodeWidth-1; i++){
				outBuffer.append("_");
			}
			outBuffer.append("_");
			for(int i = 0; i < halfNodeWidth-1; i++){
				outBuffer.append("_");
			}
			outBuffer.append("|");
			for(int i = 0; i < getChildrenWidth(node) + 1; i++){
				outBuffer.append(" ");
			}
		}
		outBuffer.append("\n");
		
		for(TestNode node : nodeMap.values()){
			int width = getNodeWidth(node);
			int halfNodeWidth = width / 2;
			
			for(int i = 0; i < halfNodeWidth; i++){
				outBuffer.append(" ");
			}
			outBuffer.append("|");
			for(int i = 0; i < halfNodeWidth; i++){
				outBuffer.append(" ");
			}
			for(int i = 0; i < getChildrenWidth(node) + 1; i++){
				outBuffer.append(" ");
			}
		}
		outBuffer.append("\n");
		
		for(TestNode node : nodeMap.values()){
			int width = getNodeWidth(node);
			int halfNodeWidth = width / 2;
			
			for(int i = 0; i < halfNodeWidth; i++){
				outBuffer.append(" ");
			}
			outBuffer.append("|");
			for(int i = 0; i < halfNodeWidth; i++){
				outBuffer.append(" ");
			}
			for(int i = 0; i < getChildrenWidth(node) + 1; i++){
				outBuffer.append(" ");
			}
		}
		outBuffer.append("\n");
		
		System.out.println(outBuffer);
	}
	
	private static boolean isEven(int n){
		if((n%2)==0){
			return true;
		}
		
		return false;
	}
	
	private static int getTreeMaxWidth(TestNode tree){
		int treeMaxWidth = getNodeWidth(tree);
		int subTreeMaxWidth = 0;
		for(TestNode node : tree.getChildMap().values()){
			subTreeMaxWidth += getTreeMaxWidth(node);			
		}
		if(subTreeMaxWidth > treeMaxWidth){
			treeMaxWidth = subTreeMaxWidth;
		}
		
		return treeMaxWidth;		
	}
	
	private static int getTreeMaxDepth(TestNode tree){
		int depth = 1;
		int childMaxDepth = 0;
		for(TestNode node : tree.getChildMap().values()){
			int subDepth = getTreeMaxDepth(node);
			if(subDepth > childMaxDepth){
				childMaxDepth = subDepth;
			}
		}
		
		return depth + childMaxDepth;
	}
	
	private static void anotherPrinter(TestNode tree){
		
	}
	
	
	
//	private static ArrayList<ArrayList<TestNode>> getNodeLayout(TestNode tree){
//		ArrayList<ArrayList<TestNode>> nodeLayout = new ArrayList<ArrayList<TestNode>>();
//		int depth = 0;
//		
//		return nodeLayout;
//	}

}
